#! /usr/bin/env python
# -*- coding: utf-8 -*-

import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import os
from pprint import pprint

class CodeDocument(QTextDocument):
    '''
    The text model
    @arg parent: The parent should be the CodeEditor
    '''
    # Segmentation fault is produced when 
    def __init__(self, parent, url = None):
        QTextDocument.__init__(self, parent)
        self._qfile = None
        
    def load(self, url):
        
        if isinstance(url, (str, QString)):
            self._qfile = QFile(url, self)
            self._qfile.open(QIODevice.Text | QIODevice.ReadWrite)
            data = self._qfile.read(self._qfile.size())
            self.setPlainText(QString(data))
            #return
            title = self._qfile.fileName().split(os.sep).last()
            # TODO: Check why title being pased as string generates SIGSEGV
            #self.setMetaInformation(QTextDocument.DocumentTitle, title)
            self.setMetaInformation(QTextDocument.DocumentTitle, str(title))
            # The document initialy is not modified
            self.setModified(False)
        else:
            print "Can't handle %s" % url
            
    def path():
        doc = "Full file path"
        def fget(self):
            return self._qfile and self._qfile.fileName() or None
        return locals()
    path = property(**path())
    
    def title():
        def fget(self):
            return self.metaInformation(QTextDocument.DocumentTitle)
        def fset(self, value):
            self.setMetaInformation( QTextDocument.DocumentTitle, value)
        return locals()
    title = property( **title() )
    
    def save(self):
        QMessageBox.information(self.parent(), 'Save', 'Save')
    
    def save_as(self):
        QFileDialog.getSaveFileName(self.parent(), "Save %s" % self.title,
                                    '', #pwd?
                                    )
        QMessageBox.information(self.parent(), 'Save as', 'Save as')
    
    def setMetaInformation(self, info, string):
        QTextDocument.setMetaInformation(self, info, string)
        # Bubble up metaInformation changes
        self.emit(SIGNAL('metaInformationUpdated'), self, info, string)
    
    
    def requestClose(self):
        '''
        If the document is modified and it's not saved, then this should
        call the save or save as functions.
        '''
        # If we don't have a QFile associated, then we're a untitled file
        if not self.isModified():
            # If it's not modified, we have nothing to save
            return True 
        if not self._qfile:
            resp = QMessageBox.question(self.parent(), "Closing %s..." % self.title,
                "%s has not been saved. Save?" % self.title,
                QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel )
            if resp == QMessageBox.Cancel:
                return False
            elif resp == QMessageBox.No:
                return True
            
            self.save_as()
            
        else:
            resp = QMessageBox.question(self.parent(), "Closing %s..." % self.title,
                "%s has been modified. Save?" % self.title,
                QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel )
            if resp == QMessageBox.Cancel:
                return False
            elif resp == QMessageBox.No:
                return True
            self.save()
            return True

class InternalEditor(QTextEdit):
    # Mimic simple style
    DEFAULT_STYLE = '''
        QTextEdit {
            selection-color: #dea;
            selection-background-color: #CBE4FA;
            selection-color: #222222;  
            border-top: 1px;
            border-left: 1px solid #888;
            padding: 0px;
            margin: 0px;
        }
    '''
    DEFAULT_FONT = QFont('Monaco', 16)
    
    def __init__(self, parent):
        assert isinstance(parent, QWidget), "Parent != QWudget???"
        QTextEdit.__init__(self, parent)
        #self.setDocument(document or Document(self))
        self.setAcceptRichText(False)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        self.setFont(self.DEFAULT_FONT)
        self.connect(self.document(), SIGNAL('blockCountChanged(int)'), 
                        parent.linenumber_col.update_width)
        self.setStyleSheet( self.DEFAULT_STYLE)
        
        
        doc = CodeDocument(self)
        
        self.setDocument(doc)
        
    def title():
        doc = "Document tilte"
        def fget(self):
            return self.document().metaInformation(QTextDocument.DocumentTitle) or "untitled"
        def fset(self, val):
            self.document().setMetaInformation(QTextDocument.DocumentTitle, val)
        return locals()
    title = property(**title())
    
    def focusInEvent(self, event):
        print event
        QTextEdit.focusInEvent(self, event)
    
    def paintEvent(self, e):
        # When the text changes, alos the left bars should
        self.parent().linenumber_col.update()
        self.parent().bookmark_col.update()
        self.parent().folding_col.update()
        QTextEdit.paintEvent(self, e)
        #print self.cursorRect()
    
    def keyPressEvent(self, e):
        QTextEdit.keyPressEvent(self,e)
    
    
    def setDocument(self, document):
        print "Document changed"
        QTextEdit.setDocument(self, document)
        self.connect(document, SIGNAL('blockCountChanged(int)'), 
                        self.parent().linenumber_col.update_width)
        # Force it!
        self.parent().linenumber_col.update_width(document.blockCount())
    
    def contextMenuRequested(self, event):
        pass
    
class LineNumberColumn(QWidget):
    
    def __init__(self, parent):
        QWidget.__init__(self, parent)
        self.__char_length = 1
        self.update_length()
        # Text color
        self._pen = QPen(QColor(0x88, 0x88, 0x88))
    
    def char_length():
        def fget(self):
            return self.__char_length
        
        def fset(self, value):
            if value != self.__char_length:
                self.__char_length = value
                self.update_length()
            
        return locals()
    char_length = property(**char_length())
    
    def update_length(self):
        #try:
            #fm = self.parent().editor.fontMetrics()
        #except:
            #fm = self.fontMetrics()
        fm = self.fontMetrics()
        self.setMinimumWidth((fm.width('w') * 1.05) * self.char_length)
        # TODO: Check this
        
    
    def update_width(self, blocks):
        length = 0
        while blocks > 0:
            blocks /= 10
            length += 1
        self.char_length = length
        
        
    def paintEvent(self, e):
        p = QPainter(self)
        p.setPen(self._pen)
        editor = self.parent().editor
        
        layout = editor.document().documentLayout()
        contentsY = editor.verticalScrollBar().value()
        pageBottom = contentsY + editor.viewport().height()
        fm = editor.fontMetrics()
        ascent = editor.fontMetrics().ascent() + 1
        lineCount = 1
        
        
        #print contentsY / fm.height()
        #print contentsY, fm.height(), fm.ascent(), fm.descent()
        
        
        # inefficient code when we reach 50K lines of code, but, who can
        # handle that length after all.
        def blockiter(start = editor.document().begin()):
            while start.isValid():
                yield start
                start = start.next()
        
        drawn = 0
        for lineCount, block in enumerate(blockiter()):
            lineCount += 1
            
            boundingRect = layout.blockBoundingRect( block )
            position = boundingRect.topLeft()
            if position.y() + boundingRect.height() < contentsY:
                continue
            if position.y() > pageBottom:
                break
            
            txt = QString.number( lineCount )
            p.drawText( self.width() - fm.width(txt), qRound( position.y() ) - contentsY + ascent, txt );
            drawn += 1
        #print "lines drawn: %d" % drawn

class BookmarkColumn(QWidget):
    DEFAULT_STYLE = '''
        border: 2px solid #dea;
    '''
    def __init__(self, parent):
        QWidget.__init__(self, parent)
        self.setMinimumWidth(self.fontMetrics().width('W'))
        self.setStyleSheet(self.DEFAULT_STYLE)
        
        
    def mousePressEvent(self, e):
        editor = self.parent().editor
        
        #layout = editor.document().documentLayout()
        contentsY = editor.verticalScrollBar().value()
        pageBottom = contentsY + editor.viewport().height()
        fm = editor.fontMetrics()
        ascent = editor.fontMetrics().ascent() + 1
        #lineCount = 1
        pprint(locals())


class FoldingColumn(QWidget):
    def __init__(self, parent):
        QWidget.__init__(self, parent)
        self.setMinimumWidth(self.fontMetrics().averageCharWidth() * 1)


class CodeEditor(QWidget):
    
    def __init__(self, parent = None, document = None,):
        '''
        QCodeEditor
        '''
        QWidget.__init__(self)
        self.layout = QHBoxLayout()
        self.layout.setMargin(0)
        self.setLayout(self.layout)
        
        self.setup_bookmark_column()
        self.setup_linenumber_column()
        self.setup_folding_column()
        self.setup_editor()
        
        self.__has_selection = False
        self.connect(self.editor, SIGNAL('copyAvailable(bool)'), 
                                            self.change_has_selection)
        
    def setup_editor(self):
        self.__editor = InternalEditor(self)
        self.layout.addWidget(self.editor)
        #self.editor = InternalEditor(self)
        
    def editor():
        doc = "Editor"
        def fget(self):
            return self.__editor
        return locals()
    editor = property(**editor())
    
    def setup_bookmark_column(self):
        self.bookmark_col = BookmarkColumn(self)
        self.layout.addWidget(self.bookmark_col)
        
    
    def setup_linenumber_column(self):
        self.linenumber_col = LineNumberColumn(self)
        self.layout.addWidget(self.linenumber_col)
    
    def setup_folding_column(self):
        self.folding_col = FoldingColumn(self)
        self.layout.addWidget(self.folding_col)
    
    def focusInEvent(self, e):
        self.editor.setFocus(Qt.TabFocusReason)
        e.accept()
        #QWidget.focusInEvent(self, e)
    
    @pyqtSignature('bool')
    def change_has_selection(self, yes):
        self.__has_selection = yes
        
    def has_selection():
        doc = "Selection available"
        def fget(self):
            return self.__has_selection
        return locals()
    has_selection = property(**has_selection())
        
    def linenumberEnabled():
        doc = "Line numbers column enabled"
        def fget(self):
            return self.linenumber_col.isVisible()
        
        def fset(self, val):
            if val:
                self.linenumber_col.show()
            else:
                self.linenumber_col.hide()
        return locals()
    linenumberEnabled = property(**linenumberEnabled())
    
    
    def bookmarkEnabled():
        doc = "Bookmark column enabled"
        def fget(self):
            return self.bookmark_col.isVisible()
        
        def fset(self, val):
            if val:
                self.bookmark_col.show()
            else:
                self.bookmark_col.hide()
        return locals()
    bookmarkEnabled = property(**bookmarkEnabled())
        
    
    def foldingEnabled():
        doc = "Folding column enabled"
        def fget(self):
            return self.folding_col.isVisible()
        def fset(self, val):
            if val:
                self.folding_col.show()
            else:
                self.folding_col.hide()
        return locals()
    foldingEnabled = property(**foldingEnabled())
    
    def zoomIn(self, range_ = 1):
        '''
        Zooms out 
        '''
        self.editor.zoomIn(range_)
    
    def zoomOut(self, range_ = 1):
        self.editor.zoomOut(range_)
    
    def requestClose(self):
        '''
        Each time a window is atteped to be closed, we shuld call the document
        '''
        #if self.editor.document().isModified():
            #resp = QMessageBox.question(self.parent(), "Close %s?" % self.editor.title, 
                    #"Save %s before closing?" % self.editor.title,
                    #QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
            #if resp == QMessageBox.Cancel:
                #return False
            #elif resp == QMessageBox.Yes:
                #print "Save()"
        return self.editor.document().requestClose()
        
class CodeEditorFactory(QObject):
    '''
    Handles the document/editor tuple creation icluding all neccesary
    signal registration, so constructor of those clases are kept simple :)
    '''
    def __init__(self, parent):
        QObject.__init__(self, parent)
        self.opened = {}
        self.tabPane = parent   # Cache parent, it wont change for this window
        print parent.parent()
        self.statusbar = parent.parent().statusbar
        # TODO: In case it's shared between many windows
        self.untitled_counter = 1
        
    def get_editor(self, qurl = None, return_widget = False):
        '''
        Factory method.
        @returns Widget index unless return_widget is set
        '''
        
        code_editor = CodeEditor( self.tabPane )
        document = code_editor.editor.document()
        
        self.connect( code_editor.editor, SIGNAL('cursorPositionChanged()'), 
                                    self.update_cursor_position)
        
        if qurl:
            scheme = qurl.scheme()
            # I hope we'll support remote editing in a near futures, that's
            # what QIODevice is intented for...
            if scheme == 'file':
                path = qurl.path() 
                if QFile.exists(path):
                    document.load(path)
                    title = document.metaInformation( document.DocumentTitle )
        else:
            title = 'Untitled %d' % self.untitled_counter
            document.setMetaInformation( document.DocumentTitle, title )
            self.untitled_counter += 1
        
        self.tabPane.addTab( code_editor, title )
        index = self.tabPane.indexOf( code_editor )
        # Change the focus it there's more than one file
        if self.tabPane.count() > 1:
            self.tabPane.setCurrentIndex(index)
        if return_widget:
            return code_editor
        return index
        
    def update_cursor_position(self):
        current = self.tabPane.currentWidget()
        if current:
            current.editor.textCursor() 